package com.theta.gateway.infra.common.utils;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.theta.gateway.infra.common.base.Result;
import com.theta.gateway.infra.common.context.GatewayRequestContext;

import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 请求处理工具类
 *
 */
@Slf4j
public class GatewayRequestUtil {
	
	private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    
    public static Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayRequestContext gatewayContext) {
        
    	
    	/**
         * join the body
         */
        return DataBufferUtils.join(exchange.getRequest().getBody())
        		.flatMap(dataBuffer -> {
            /*
             * read the body Flux<DataBuffer>, and release the buffer
             */
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.retain(buffer);
                return Mono.just(buffer);
            });
            /**
             * repackage ServerHttpRequest
             */
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                	cachedFlux.subscribe(buffer->{
                		 CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
                         DataBufferUtils.release(buffer);
                         gatewayContext.setCacheBody(charBuffer.toString());
                	});
                    return cachedFlux;
                }
            };
//            ResponseDecorator decorator = new ResponseDecorator(exchange.getResponse());
            /**
             * mutate exchage with new ServerHttpRequest
             */
            ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
            /**
             * read body string with default messageReaders
             */
            return ServerRequest.create(mutatedExchange, messageReaders)
            		.bodyToMono(String.class)
//                .doOnNext(objectValue -> {
//                    gatewayContext.setCacheBody(objectValue);
//                    log.info("[GatewayContext]Read JsonBody:{}", objectValue);
//                })
                .then(chain.filter(mutatedExchange))
                ;
        });
    }
    
    
    /**
     * ReadFormData
     *
     * @param exchange
     * @param chain
     * @return
     */
    public static Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain,
        GatewayRequestContext gatewayContext) {
        final ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        
        return exchange.getFormData().doOnNext(multiValueMap -> {
            gatewayContext.setFormData(multiValueMap);
            log.debug("[GatewayContext]Read FormData:{}", multiValueMap);
        }).then(Mono.defer(() -> {
            Charset charset = headers.getContentType().getCharset();
            charset = charset == null ? StandardCharsets.UTF_8 : charset;
            String charsetName = charset.name();
            MultiValueMap<String, String> formData = gatewayContext.getFormData();
            /**
             * formData is empty just return
             */
            if (null == formData || formData.isEmpty()) {
                return chain.filter(exchange);
            }
            StringBuilder formDataBodyBuilder = new StringBuilder();
            String entryKey;
            List<String> entryValue;
            try {
                /**
                 * repackage form data
                 */
                for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
                    entryKey = entry.getKey();
                    entryValue = entry.getValue();
                    if (entryValue.size() > 1) {
                        for (String value : entryValue) {
                            formDataBodyBuilder.append(entryKey).append("=").append(URLEncoder.encode(value, charsetName)).append("&");
                        }
                    } else {
                        formDataBodyBuilder.append(entryKey).append("=").append(URLEncoder.encode(entryValue.get(0), charsetName)).append("&");
                    }
                }
            } catch (UnsupportedEncodingException e) {
                // ignore URLEncode Exception
            }
            /**
             * substring with the last char '&'
             */
            String formDataBodyString = "";
            if (formDataBodyBuilder.length() > 0) {
                formDataBodyString = formDataBodyBuilder.substring(0, formDataBodyBuilder.length() - 1);
            }
            /**
             * get data bytes
             */
            byte[] bodyBytes = formDataBodyString.getBytes(charset);
            int contentLength = bodyBytes.length;
            ServerHttpRequest decorator = new ServerHttpRequestDecorator(request) {
                /**
                 * change content-length
                 * @return
                 */
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }

                /**
                 * read bytes to Flux<Databuffer>
                 * @return
                 */
                @Override
                public Flux<DataBuffer> getBody() {
                    return DataBufferUtils.read(new ByteArrayResource(bodyBytes),
                        new NettyDataBufferFactory(ByteBufAllocator.DEFAULT), contentLength);
                }
            };
            ServerWebExchange mutateExchange = exchange.mutate().request(decorator).build();
            log.info("[GatewayContext]Rewrite Form Data :{}", formDataBodyString);
            return chain.filter(mutateExchange);
        }));
    }
    
    
    /**
     * 禁止访问
     *
     * @param response
     * @return
     */
    @SuppressWarnings("unused")
	public static Mono<Void> responseJson(ServerHttpResponse response, Result result) {
        byte[] datas = JSON.toJSONBytes(result);
        DataBuffer buffer = response.bufferFactory().wrap(datas);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
    
    
    /**
	 * 获取header
	 * @param request
	 * @param headerName
	 * @return
	 */
	public static String getHeader(ServerHttpRequest request,String headerName) {
		HttpHeaders httpHeaders = request.getHeaders();
        List<String> headerTokens = httpHeaders.get(headerName);
        if (!CollectionUtils.isEmpty(headerTokens)) {
            return headerTokens.get(0);
        }
        return "";
		
	}
	
	/**
	 * 请求头中获取token
	 * @param request
	 * @param tokenName
	 * @return
	 */
	public static String getToken(ServerHttpRequest request,String tokenName) {
		//从请求头中获取token
		String header=getHeader(request, tokenName);
		if(StringUtils.isBlank(header)) {
			//从cookie中获取token
			HttpCookie tokenCookies = request.getCookies().getFirst(tokenName);
	        if (tokenCookies != null) {
	        	header= tokenCookies.getValue();
	        }
	        //从请求参数中获取token
	        if(StringUtils.isBlank(header)) {
	        	header = request.getQueryParams().getFirst(tokenName);
	        }
		}
		return header;
		
	}
	
	/**
	 * -校验请求URL是否在url列表中
	 * @param url
	 * @param urls
	 * @return
	 */
	public static boolean checkUrlMatchPatterns(String url,List<String> urlPatterns) {
		boolean isIncluded = false;
		if (urlPatterns == null || urlPatterns.isEmpty()) {
			return isIncluded;
		} else {
			for (String eurl : urlPatterns) {
				// 匹配成功则返回true
				if (StrUtil.matches(eurl, url)) {
					isIncluded = true;
					break;
				}
			}
			return isIncluded;
		}
	}
	
	/**
	 * 校验URL 是否 符合指定规则
	 * @param url
	 * @param urlPattern
	 * @return
	 */
	public static boolean checkUrlMatchPattern(String url, String urlPattern) {
		log.info("checkUrlMatchPattern===>{},{}",url,urlPattern);
		boolean isMatch=false;
		//url匹配规则不为空则开始进行匹配处理
		if(StrUtil.isNotEmpty(urlPattern)) {
			List<String> urlPatterns = Lists.newArrayList(urlPattern.split(","));
			if (checkUrlMatchPatterns(url, urlPatterns)) {
				log.warn("当前url:{}符合url规则：{}", url, urlPattern);
				isMatch=true;
			}
		}
		return isMatch;
	}

}
